﻿using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;

namespace Molten;

/// <summary>
/// Represents a 32-bit color (4 bytes) in the form of RGBA (in byte order: R, G, B, A).
/// </summary>
[StructLayout(LayoutKind.Explicit)]
[Serializable]
public partial struct Color : IEquatable<Color>, IFormattable
{
    private const string toStringFormat = "A:{0} R:{1} G:{2} B:{3}";

    /// <summary>
    /// The red component of the color.
    /// </summary>
    [DataMember]
    [FieldOffset(0)]
    public byte R;

    /// <summary>
    /// The green component of the color.
    /// </summary>
    [DataMember]
    [FieldOffset(1)]
    public byte G;

    /// <summary>
    /// The blue component of the color.
    /// </summary>
    [DataMember]
    [FieldOffset(2)]
    public byte B;

    /// <summary>
    /// The alpha component of the color.
    /// </summary>
    [DataMember]
    [FieldOffset(3)]
    public byte A;

    /// <summary>
    /// Represents the four components, RGBA, in an array. Overlaps the individual component fields in memory.
    /// </summary>
    [IgnoreDataMember]
    [FieldOffset(0)]
    public unsafe fixed byte Values[4];

    /// <summary>
    /// Represents the four components, RGBA in an integer. Overlaps the individual component fields in memory.
    /// </summary>
    [IgnoreDataMember]
    [FieldOffset(0)]
    public int Packed; 

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="value">The value that will be assigned to all components.</param>
    public Color(byte value)
    {
        A = R = G = B = value;
    }

    /// <summary>
    /// Initializes a new instance of <see cref="Color"/> using a hexidecimal color code. Both 8-digit and 6-digit hex colors are accepted for RGBA and RGB. <para/>
    /// Strings must contain only valid hexidecimal color characters, such as #, A-F, a-f and 0-9. 
    /// For example, "#2d2d30", "2d2d30FF" and "2d2d30" are all valid inputs. Casing is ignored.
    /// </summary>
    /// <param name="hexColor">The hexidecimal color value (e.g. #FF0000FF for red). 3-channel (RGB) and 4-channel (RGBA) values are accepted.</param>
    public Color(string hexColor)
    {
        hexColor = hexColor.Replace("#", "");

        if (hexColor.Length < 6)
            hexColor = hexColor + new string('0', 6 - hexColor.Length) + "FF";
        else if (hexColor.Length == 6)
            hexColor += "FF"; // Just add the alpha channel.
        else if(hexColor.Length < 8)
            hexColor += new string('0', 8 - hexColor.Length);

        int rgba = 0;
        if (int.TryParse(hexColor, NumberStyles.HexNumber, null, out rgba) == false)
            throw new Exception("The provided hexidecimal string was invalid. It must contain a maximum of 9 hexidecimal color characters (#, 0-9, a-f or A-F).");

        A = (byte)(rgba << 24 >> 24);
        B = (byte)(rgba << 16 >> 24);
        G = (byte)(rgba << 8 >> 24);
        R = (byte)(rgba >> 24);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="value">The value that will be assigned to all components.</param>
    public Color(float value)
    {
        A = R = G = B = ToByte(value);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="red">The red component of the color.</param>
    /// <param name="green">The green component of the color.</param>
    /// <param name="blue">The blue component of the color.</param>
    /// <param name="alpha">The alpha component of the color.</param>
    public Color(byte red, byte green, byte blue, byte alpha)
    {
        R = red;
        G = green;
        B = blue;
        A = alpha;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.  Alpha is set to 255.
    /// </summary>
    /// <param name="red">The red component of the color.</param>
    /// <param name="green">The green component of the color.</param>
    /// <param name="blue">The blue component of the color.</param>
    public Color(byte red, byte green, byte blue)
    {
        R = red;
        G = green;
        B = blue;
        A = 255;
    }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.  Passed values are clamped within byte range.
    /// </summary>
    /// <param name="red">The red component of the color.</param>
    /// <param name="green">The green component of the color.</param>
    /// <param name="blue">The blue component of the color.</param>
    public Color(int red, int green, int blue, int alpha)
    {
        R = ToByte(red);
        G = ToByte(green);
        B = ToByte(blue);
        A = ToByte(alpha);
    }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.  Alpha is set to 255.  Passed values are clamped within byte range.
    /// </summary>
    /// <param name="red">The red component of the color.</param>
    /// <param name="green">The green component of the color.</param>
    /// <param name="blue">The blue component of the color.</param>
    public Color(int red, int green, int blue)
        : this (red, green, blue, 255) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="red">The red component of the color.</param>
    /// <param name="green">The green component of the color.</param>
    /// <param name="blue">The blue component of the color.</param>
    /// <param name="alpha">The alpha component of the color.</param>
    public Color(float red, float green, float blue, float alpha)
    {
        R = ToByte(red);
        G = ToByte(green);
        B = ToByte(blue);
        A = ToByte(alpha);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.  Alpha is set to 255.
    /// </summary>
    /// <param name="red">The red component of the color.</param>
    /// <param name="green">The green component of the color.</param>
    /// <param name="blue">The blue component of the color.</param>
    public Color(float red, float green, float blue)
    {
        R = ToByte(red);
        G = ToByte(green);
        B = ToByte(blue);
        A = 255;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="value">The red, green, blue, and alpha components of the color.</param>
    public Color(Vector4F value)
    {
        R = ToByte(value.X);
        G = ToByte(value.Y);
        B = ToByte(value.Z);
        A = ToByte(value.W);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="value">The red, green, and blue components of the color.</param>
    /// <param name="alpha">The alpha component of the color.</param>
    public Color(Vector3F value, float alpha)
    {
        R = ToByte(value.X);
        G = ToByte(value.Y);
        B = ToByte(value.Z);
        A = ToByte(alpha);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct. Alpha is set to 255.
    /// </summary>
    /// <param name="value">The red, green, and blue components of the color.</param>
    public Color(Vector3F value)
    {
        R = ToByte(value.X);
        G = ToByte(value.Y);
        B = ToByte(value.Z);
        A = 255;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="rgba">A packed integer containing all four color components in RGBA order.</param>
    public Color(uint rgba)
    {
        A = (byte)((rgba >> 24) & 255);
        B = (byte)((rgba >> 16) & 255);
        G = (byte)((rgba >> 8) & 255);
        R = (byte)(rgba & 255);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="rgba">A packed integer containing all four color components in RGBA order.</param>
    public Color(int rgba)
    {
        A = (byte)((rgba >> 24) & 255);
        B = (byte)((rgba >> 16) & 255);
        G = (byte)((rgba >> 8) & 255);
        R = (byte)(rgba & 255);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="values">The values to assign to the red, green, and blue, alpha components of the color. This must be an array with four elements.</param>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="values"/> is <c>null</c>.</exception>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="values"/> contains more or less than four elements.</exception>
    public Color(float[] values)
    {
        if (values == null)
            throw new ArgumentNullException("values");
        if (values.Length != 4)
            throw new ArgumentOutOfRangeException("values", "There must be four and only four input values for Color.");

        R = ToByte(values[0]);
        G = ToByte(values[1]);
        B = ToByte(values[2]);
        A = ToByte(values[3]);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Color"/> struct.
    /// </summary>
    /// <param name="values">The values to assign to the alpha, red, green, and blue components of the color. This must be an array with four elements.</param>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="values"/> is <c>null</c>.</exception>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="values"/> contains more or less than four elements.</exception>
    public Color(byte[] values)
    {
        if (values == null)
            throw new ArgumentNullException("values");
        if (values.Length != 4)
            throw new ArgumentOutOfRangeException("values", "There must be four and only four input values for Color.");

        R = values[0];
        G = values[1];
        B = values[2];
        A = values[3];
    }

    /// <summary>
    /// Gets or sets the component at the specified index.
    /// </summary>
    /// <value>The value of the alpha, red, green, or blue component, depending on the index.</value>
    /// <param name="index">The index of the component to access. Use 0 for the alpha component, 1 for the red component, 2 for the green component, and 3 for the blue component.</param>
    /// <returns>The value of the component at the specified index.</returns>
    /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the <paramref name="index"/> is out of the range [0, 3].</exception>
    public unsafe byte this[int index]
    {
        get
        {
            if (index > 3 || index < 0)
                throw new IndexOutOfRangeException("Index for Color must be between from 0 to 3, inclusive.");

            return Values[index];
        }
        set
        {
            if (index > 3 || index < 0)
                throw new IndexOutOfRangeException("Index for Color must be between from 0 to 3, inclusive.");

            Values[index] = value;
        }
    }

    /// <summary>
    /// Gets or sets the component at the specified index.
    /// </summary>
    /// <value>The value of the alpha, red, green, or blue component, depending on the index.</value>
    /// <param name="index">The index of the component to access. Use 0 for the alpha component, 1 for the red component, 2 for the green component, and 3 for the blue component.</param>
    /// <returns>The value of the component at the specified index.</returns>
    /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the <paramref name="index"/> is out of the range [0, 3].</exception>
    public unsafe byte this[uint index]
    {
        get
        {
            if (index > 3 || index < 0)
                throw new IndexOutOfRangeException("Index for Color must be between from 0 to 3, inclusive.");

            return Values[index];
        }
        set
        {
            if (index > 3 || index < 0)
                throw new IndexOutOfRangeException("Index for Color must be between from 0 to 3, inclusive.");

            Values[index] = value;
        }
    }

    /// <summary>
    /// Converts the color into a packed integer.
    /// </summary>
    /// <returns>A packed integer containing all four color components.</returns>
    public int ToBgra()
    {
        int value = B;
        value |= G << 8;
        value |= R << 16;
        value |= A << 24;

        return value;
    }

    /// <summary>
    /// Converts the color into a packed integer.
    /// </summary>
    /// <returns>A packed integer containing all four color components.</returns>
    public int ToRgba()
    {
        int value = R;
        value |= G << 8;
        value |= B << 16;
        value |= A << 24;

        return value;
    }

    /// <summary>
    /// Converts the color into a packed integer.
    /// </summary>
    /// <returns>A packed integer containing all four color components.</returns>
    public int ToAbgr()
    {
        int value = A;
        value |= B << 8;
        value |= G << 16;
        value |= R << 24;

        return value;
    }

    /// <summary>
    /// Creates an array containing the elements of the color.
    /// </summary>
    /// <returns>A four-element array containing the components of the color in RGBA order.</returns>
    public byte[] ToArray() => [R, G, B, A];

    /// <summary>
    /// Gets the brightness.
    /// </summary>
    /// <returns>The Hue-Saturation-Brightness (HSB) saturation for this <see cref="Color"/></returns>
    public float GetBrightness()
    {
        float r = R / 255.0f;
        float g = G / 255.0f;
        float b = B / 255.0f;

        float max, min;

        max = r; min = r;

        if (g > max) max = g;
        if (b > max) max = b;

        if (g < min) min = g;
        if (b < min) min = b;

        return (max + min) / 2;
    }

    /// <summary>
    /// Gets the hue.
    /// </summary>
    /// <returns>The Hue-Saturation-Brightness (HSB) saturation for this <see cref="Color"/></returns>
    public float GetHue()
    {
        if (R == G && G == B)
            return 0; // 0 makes as good an UNDEFINED value as any

        float r = R / 255.0f;
        float g = G / 255.0f;
        float b = B / 255.0f;

        float max, min;
        float delta;
        float hue = 0.0f;

        max = r; min = r;

        if (g > max) max = g;
        if (b > max) max = b;

        if (g < min) min = g;
        if (b < min) min = b;

        delta = max - min;

        if (r == max)
            hue = (g - b) / delta;
        else if (g == max)
            hue = 2 + (b - r) / delta;
        else if (b == max)
            hue = 4 + (r - g) / delta;

        hue *= 60;

        if (hue < 0.0f)
            hue += 360.0f;

        return hue;
    }

    /// <summary>
    /// Gets the saturation.
    /// </summary>
    /// <returns>The Hue-Saturation-Brightness (HSB) saturation for this <see cref="Color"/></returns>
    public float GetSaturation()
    {
        float r = R / 255.0f;
        float g = G / 255.0f;
        float b = B / 255.0f;

        float max, min;
        float l, s = 0;

        max = r; min = r;

        if (g > max) max = g;
        if (b > max) max = b;

        if (g < min) min = g;
        if (b < min) min = b;

        // if max == min, then there is no color and
        // the saturation is zero.
        //
        if (max != min)
        {
            l = (max + min) / 2;

            if (l <= .5)
                s = (max - min) / (max + min);
            else
                s = (max - min) / (2 - max - min);
        }
        return s;
    }

    /// <summary>Converts the color into a packed unsigned integer, then returns it.</summary>
    /// <returns></returns>
    public uint GetPacked()
    {
        uint result = R;
        result += (uint)G >> 8;
        result += (uint)B >> 16;
        result += (uint)A >> 24;

        return result;
    }

    public static Color SetR(Color old, byte red)
    {
        return new Color(red, old.G, old.B, old.A);
    }

    public static Color SetG(Color old, byte green)
    {
        return new Color(old.R, green, old.B, old.A);
    }

    public static Color SetB(Color old, byte blue)
    {
        return new Color(old.R, old.G, blue, blue);
    }

    public static Color SetA(Color old, byte alpha)
    {
        return new Color(old.R, old.G, old.B, alpha);
    }

    /// <summary>
    /// Adds two colors.
    /// </summary>
    /// <param name="left">The first color to add.</param>
    /// <param name="right">The second color to add.</param>
    /// <param name="result">When the method completes, completes the sum of the two colors.</param>
    public static void Add(ref Color left, ref Color right, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = (byte)(left.A + right.A);
        result.R = (byte)(left.R + right.R);
        result.G = (byte)(left.G + right.G);
        result.B = (byte)(left.B + right.B);
    }

    /// <summary>
    /// Subtracts two colors.
    /// </summary>
    /// <param name="left">The first color to subtract.</param>
    /// <param name="right">The second color to subtract.</param>
    /// <param name="result">WHen the method completes, contains the difference of the two colors.</param>
    public static void Subtract(ref Color left, ref Color right, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = (byte)(left.A - right.A);
        result.R = (byte)(left.R - right.R);
        result.G = (byte)(left.G - right.G);
        result.B = (byte)(left.B - right.B);
    }

    /// <summary>
    /// Modulates two colors.
    /// </summary>
    /// <param name="left">The first color to modulate.</param>
    /// <param name="right">The second color to modulate.</param>
    /// <param name="result">When the method completes, contains the modulated color.</param>
    public static void Modulate(ref Color left, ref Color right, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = (byte)(left.A * right.A / 255.0f);
        result.R = (byte)(left.R * right.R / 255.0f);
        result.G = (byte)(left.G * right.G / 255.0f);
        result.B = (byte)(left.B * right.B / 255.0f);
    }

    /// <summary>
    /// Scales a color.
    /// </summary>
    /// <param name="value">The color to scale.</param>
    /// <param name="scale">The amount by which to scale.</param>
    /// <param name="result">When the method completes, contains the scaled color.</param>
    public static void Scale(ref Color value, float scale, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = (byte)(value.A * scale);
        result.R = (byte)(value.R * scale);
        result.G = (byte)(value.G * scale);
        result.B = (byte)(value.B * scale);
    }

    /// <summary>
    /// Scales a color.
    /// </summary>
    /// <param name="value">The color to scale.</param>
    /// <param name="scale">The amount by which to scale.</param>
    /// <returns>The scaled color.</returns>
    public static Color Scale(Color value, float scale)
    {
        return new Color((byte)(value.R * scale), (byte)(value.G * scale), (byte)(value.B * scale), (byte)(value.A * scale));
    }

    /// <summary>
    /// Negates a color.
    /// </summary>
    /// <param name="value">The color to negate.</param>
    /// <param name="result">When the method completes, contains the negated color.</param>
    public static void Negate(ref Color value, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = (byte)(255 - value.A);
        result.R = (byte)(255 - value.R);
        result.G = (byte)(255 - value.G);
        result.B = (byte)(255 - value.B);
    }

    /// <summary>
    /// Negates a color.
    /// </summary>
    /// <param name="value">The color to negate.</param>
    /// <returns>The negated color.</returns>
    public static Color Negate(Color value)
    {
        return new Color(255 - value.R, 255 - value.G, 255 - value.B, 255 - value.A);
    }

    /// <summary>
    /// Restricts a value to be within a specified range.
    /// </summary>
    /// <param name="value">The value to clamp.</param>
    /// <param name="min">The minimum value.</param>
    /// <param name="max">The maximum value.</param>
    /// <param name="result">When the method completes, contains the clamped value.</param>
    public static void Clamp(ref Color value, ref Color min, ref Color max, out Color result)
    {
        byte alpha = value.A;
        alpha = (alpha > max.A) ? max.A : alpha;
        alpha = (alpha < min.A) ? min.A : alpha;

        byte red = value.R;
        red = (red > max.R) ? max.R : red;
        red = (red < min.R) ? min.R : red;

        byte green = value.G;
        green = (green > max.G) ? max.G : green;
        green = (green < min.G) ? min.G : green;

        byte blue = value.B;
        blue = (blue > max.B) ? max.B : blue;
        blue = (blue < min.B) ? min.B : blue;

        result = new Color(red, green, blue, alpha);
    }

    /// <summary>
    /// Computes the premultiplied value of the provided color.
    /// </summary>
    /// <param name="value">The non-premultiplied value.</param>
    /// <param name="result">The premultiplied result.</param>
    public static void Premultiply(ref Color value, out Color result)
    {
        Unsafe.SkipInit(out result);

        var a = value.A / (255f * 255f);
        result.A = value.A;
        result.R = ToByte(value.R * a);
        result.G = ToByte(value.G * a);
        result.B = ToByte(value.B * a);
    }

    /// <summary>
    /// Computes the premultiplied value of the provided color.
    /// </summary>
    /// <param name="value">The non-premultiplied value.</param>
    /// <returns>The premultiplied result.</returns>
    public static Color Premultiply(Color value)
    {
        Color result;
        Premultiply(ref value, out result);
        return result;
    }

    /// <summary>
    /// Converts the color from a packed BGRA integer.
    /// </summary>
    /// <param name="color">A packed integer containing all four color components in BGRA order</param>
    /// <returns>A color.</returns>
    public static Color FromBgra(int color)
    {
        return new Color((byte)((color >> 16) & 255), (byte)((color >> 8) & 255), (byte)(color & 255), (byte)((color >> 24) & 255));
    }

    /// <summary>
    /// Converts the color from a packed BGRA integer.
    /// </summary>
    /// <param name="color">A packed integer containing all four color components in BGRA order</param>
    /// <returns>A color.</returns>
    public static Color FromBgra(uint color)
    {
        return FromBgra(unchecked((int) color));
    }

    /// <summary>
    /// Converts the color from a packed ABGR integer.
    /// </summary>
    /// <param name="color">A packed integer containing all four color components in ABGR order</param>
    /// <returns>A color.</returns>
    public static Color FromAbgr(int color)
    {
        return new Color((byte)(color >> 24), (byte)(color >> 16), (byte)(color >> 8), (byte)color);
    }

    /// <summary>
    /// Converts the color from a packed ABGR integer.
    /// </summary>
    /// <param name="color">A packed integer containing all four color components in ABGR order</param>
    /// <returns>A color.</returns>
    public static Color FromAbgr(uint color)
    {
        return FromAbgr(unchecked((int)color));
    }

    /// <summary>
    /// Converts the color from a packed BGRA integer.
    /// </summary>
    /// <param name="color">A packed integer containing all four color components in RGBA order</param>
    /// <returns>A color.</returns>
    public static Color FromRgba(int color)
    {
        return new Color(color);
    }

    /// <summary>
    /// Converts the color from a packed BGRA integer.
    /// </summary>
    /// <param name="color">A packed integer containing all four color components in RGBA order</param>
    /// <returns>A color.</returns>
    public static Color FromRgba(uint color)
    {
        return new Color(color);
    }

    /// <summary>
    /// Restricts a value to be within a specified range.
    /// </summary>
    /// <param name="value">The value to clamp.</param>
    /// <param name="min">The minimum value.</param>
    /// <param name="max">The maximum value.</param>
    /// <returns>The clamped value.</returns>
    public static Color Clamp(Color value, Color min, Color max)
    {
        Color result;
        Clamp(ref value, ref min, ref max, out result);
        return result;
    }

    /// <summary>
    /// Performs a linear interpolation between two colors.
    /// </summary>
    /// <param name="start">Start color.</param>
    /// <param name="end">End color.</param>
    /// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
    /// <param name="result">When the method completes, contains the linear interpolation of the two colors.</param>
    /// <remarks>
    /// Passing <paramref name="amount"/> a value of 0 will cause <paramref name="start"/> to be returned; a value of 1 will cause <paramref name="end"/> to be returned. 
    /// </remarks>
    public static void Lerp(ref Color start, ref Color end, float amount, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.R = MathHelper.Lerp(start.R, end.R, amount);
        result.G = MathHelper.Lerp(start.G, end.G, amount);
        result.B = MathHelper.Lerp(start.B, end.B, amount);
        result.A = MathHelper.Lerp(start.A, end.A, amount);
    }

    /// <summary>
    /// Performs a linear interpolation between two colors.
    /// </summary>
    /// <param name="start">Start color.</param>
    /// <param name="end">End color.</param>
    /// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
    /// <returns>The linear interpolation of the two colors.</returns>
    /// <remarks>
    /// Passing <paramref name="amount"/> a value of 0 will cause <paramref name="start"/> to be returned; a value of 1 will cause <paramref name="end"/> to be returned. 
    /// </remarks>
    public static Color Lerp(Color start, Color end, float amount)
    {
        Color result;
        Lerp(ref start, ref end, amount, out result);
        return result;
    }

    /// <summary>
    /// Performs a cubic interpolation between two colors.
    /// </summary>
    /// <param name="start">Start color.</param>
    /// <param name="end">End color.</param>
    /// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
    /// <param name="result">When the method completes, contains the cubic interpolation of the two colors.</param>
    public static void SmoothStep(ref Color start, ref Color end, float amount, out Color result)
    {
        amount = MathHelper.SmoothStep(amount);
        Lerp(ref start, ref end, amount, out result);
    }

    /// <summary>
    /// Performs a cubic interpolation between two colors.
    /// </summary>
    /// <param name="start">Start color.</param>
    /// <param name="end">End color.</param>
    /// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
    /// <returns>The cubic interpolation of the two colors.</returns>
    public static Color SmoothStep(Color start, Color end, float amount)
    {
        Color result;
        SmoothStep(ref start, ref end, amount, out result);
        return result;
    }

    /// <summary>
    /// Returns a color containing the smallest components of the specified colors.
    /// </summary>
    /// <param name="left">The first source color.</param>
    /// <param name="right">The second source color.</param>
    /// <param name="result">When the method completes, contains an new color composed of the largest components of the source colors.</param>
    public static void Max(ref Color left, ref Color right, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = (left.A > right.A) ? left.A : right.A;
        result.R = (left.R > right.R) ? left.R : right.R;
        result.G = (left.G > right.G) ? left.G : right.G;
        result.B = (left.B > right.B) ? left.B : right.B;
    }

    /// <summary>
    /// Returns a color containing the largest components of the specified colorss.
    /// </summary>
    /// <param name="left">The first source color.</param>
    /// <param name="right">The second source color.</param>
    /// <returns>A color containing the largest components of the source colors.</returns>
    public static Color Max(Color left, Color right)
    {
        Color result;
        Max(ref left, ref right, out result);
        return result;
    }

    /// <summary>
    /// Returns a color containing the smallest components of the specified colors.
    /// </summary>
    /// <param name="left">The first source color.</param>
    /// <param name="right">The second source color.</param>
    /// <param name="result">When the method completes, contains an new color composed of the smallest components of the source colors.</param>
    public static void Min(ref Color left, ref Color right, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = (left.A < right.A) ? left.A : right.A;
        result.R = (left.R < right.R) ? left.R : right.R;
        result.G = (left.G < right.G) ? left.G : right.G;
        result.B = (left.B < right.B) ? left.B : right.B;
    }

    /// <summary>
    /// Returns a color containing the smallest components of the specified colors.
    /// </summary>
    /// <param name="left">The first source color.</param>
    /// <param name="right">The second source color.</param>
    /// <returns>A color containing the smallest components of the source colors.</returns>
    public static Color Min(Color left, Color right)
    {
        Color result;
        Min(ref left, ref right, out result);
        return result;
    }

    /// <summary>
    /// Adjusts the contrast of a color.
    /// </summary>
    /// <param name="value">The color whose contrast is to be adjusted.</param>
    /// <param name="contrast">The amount by which to adjust the contrast.</param>
    /// <param name="result">When the method completes, contains the adjusted color.</param>
    public static void AdjustContrast(ref Color value, float contrast, out Color result)
    {
        Unsafe.SkipInit(out result);

        result.A = value.A;
        result.R = ToByte(0.5f + contrast * (value.R / 255.0f - 0.5f));
        result.G = ToByte(0.5f + contrast * (value.G / 255.0f - 0.5f));
        result.B = ToByte(0.5f + contrast * (value.B / 255.0f - 0.5f));
    }

    /// <summary>
    /// Adjusts the contrast of a color.
    /// </summary>
    /// <param name="value">The color whose contrast is to be adjusted.</param>
    /// <param name="contrast">The amount by which to adjust the contrast.</param>
    /// <returns>The adjusted color.</returns>
    public static Color AdjustContrast(Color value, float contrast)
    {
        return new Color(                
            ToByte(0.5f + contrast * (value.R / 255.0f - 0.5f)),
            ToByte(0.5f + contrast * (value.G / 255.0f - 0.5f)),
            ToByte(0.5f + contrast * (value.B / 255.0f- 0.5f)),
            value.A);
    }

    /// <summary>
    /// Adjusts the saturation of a color.
    /// </summary>
    /// <param name="value">The color whose saturation is to be adjusted.</param>
    /// <param name="saturation">The amount by which to adjust the saturation.</param>
    /// <param name="result">When the method completes, contains the adjusted color.</param>
    public static void AdjustSaturation(ref Color value, float saturation, out Color result)
    {
        Unsafe.SkipInit(out result);

        float grey = value.R  / 255.0f * 0.2125f + value.G / 255.0f * 0.7154f + value.B / 255.0f * 0.0721f;

        result.A = value.A;
        result.R = ToByte(grey + saturation * (value.R / 255.0f - grey));
        result.G = ToByte(grey + saturation * (value.G / 255.0f- grey));
        result.B = ToByte(grey + saturation * (value.B / 255.0f - grey));
    }

    /// <summary>
    /// Adjusts the saturation of a color.
    /// </summary>
    /// <param name="value">The color whose saturation is to be adjusted.</param>
    /// <param name="saturation">The amount by which to adjust the saturation.</param>
    /// <returns>The adjusted color.</returns>
    public static Color AdjustSaturation(Color value, float saturation)
    {
        float grey = value.R / 255.0f * 0.2125f + value.G / 255.0f * 0.7154f + value.B / 255.0f * 0.0721f;

        return new Color(                
            ToByte(grey + saturation * (value.R / 255.0f - grey)),
            ToByte(grey + saturation * (value.G / 255.0f - grey)),
            ToByte(grey + saturation * (value.B / 255.0f - grey)),
            value.A);
    }

    /// <summary>
    /// Adds two colors.
    /// </summary>
    /// <param name="left">The first color to add.</param>
    /// <param name="right">The second color to add.</param>
    /// <returns>The sum of the two colors.</returns>
    public static Color operator +(Color left, Color right)
    {
        return new Color(left.R + right.R, left.G + right.G, left.B + right.B, left.A + right.A);
    }

    /// <summary>
    /// Assert a color (return it unchanged).
    /// </summary>
    /// <param name="value">The color to assert (unchanged).</param>
    /// <returns>The asserted (unchanged) color.</returns>
    public static Color operator +(Color value)
    {
        return value;
    }

    /// <summary>
    /// Subtracts two colors.
    /// </summary>
    /// <param name="left">The first color to subtract.</param>
    /// <param name="right">The second color to subtract.</param>
    /// <returns>The difference of the two colors.</returns>
    public static Color operator -(Color left, Color right)
    {
        return new Color(left.R - right.R, left.G - right.G, left.B - right.B, left.A - right.A);
    }

    /// <summary>
    /// Negates a color.
    /// </summary>
    /// <param name="value">The color to negate.</param>
    /// <returns>A negated color.</returns>
    public static Color operator -(Color value)
    {
        return new Color(-value.R, -value.G, -value.B, -value.A);
    }

    /// <summary>
    /// Scales a color.
    /// </summary>
    /// <param name="scale">The factor by which to scale the color.</param>
    /// <param name="value">The color to scale.</param>
    /// <returns>The scaled color.</returns>
    public static Color operator *(float scale, Color value)
    {
        return new Color((byte)(value.R * scale), (byte)(value.G * scale), (byte)(value.B * scale), (byte)(value.A * scale));
    }

    /// <summary>
    /// Scales a color.
    /// </summary>
    /// <param name="value">The factor by which to scale the color.</param>
    /// <param name="scale">The color to scale.</param>
    /// <returns>The scaled color.</returns>
    public static Color operator *(Color value, float scale)
    {
        return new Color((byte)(value.R * scale), (byte)(value.G * scale), (byte)(value.B * scale), (byte)(value.A * scale));
    }

    /// <summary>
    /// Modulates two colors.
    /// </summary>
    /// <param name="left">The first color to modulate.</param>
    /// <param name="right">The second color to modulate.</param>
    /// <returns>The modulated color.</returns>
    public static Color operator *(Color left, Color right)
    {
        Modulate(ref left, ref right, out Color result);
        return result;
    }

    /// <summary>
    /// Tests for equality between two objects.
    /// </summary>
    /// <param name="left">The first value to compare.</param>
    /// <param name="right">The second value to compare.</param>
    /// <returns><c>true</c> if <paramref name="left"/> has the same value as <paramref name="right"/>; otherwise, <c>false</c>.</returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator ==(Color left, Color right)
    {
        return left.Packed == right.Packed;
    }

    /// <summary>
    /// Tests for inequality between two objects.
    /// </summary>
    /// <param name="left">The first value to compare.</param>
    /// <param name="right">The second value to compare.</param>
    /// <returns><c>true</c> if <paramref name="left"/> has a different value than <paramref name="right"/>; otherwise, <c>false</c>.</returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator !=(Color left, Color right)
    {
        return left.Packed != right.Packed;
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="Color"/> to <see cref="Color3"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static explicit operator Color3(Color value)
    {
        return new Color3(value.R / 255.0f, value.G / 255.0f, value.B / 255.0f);
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="Color"/> to <see cref="Vector3F"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static explicit operator Vector3F(Color value)
    {
        return new Vector3F(value.R / 255.0f, value.G / 255.0f, value.B / 255.0f);
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="Color"/> to <see cref="Vector4F"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static explicit operator Vector4F(Color value)
    {
        return new Vector4F(value.R / 255.0f, value.G / 255.0f, value.B / 255.0f, value.A / 255.0f);
    }

    /// <summary>
    /// Convert this instance to a <see cref="Color4"/>
    /// </summary>
    /// <returns>The result of the conversion.</returns>
    public Color4 ToColor4()
    {
        return new Color4(R / 255.0f, G / 255.0f, B / 255.0f, A / 255.0f);
    }

    /// <summary>
    /// Performs an implicit conversion from <see cref="Color"/> to <see cref="Color4"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static implicit operator Color4(Color value)
    {
        return value.ToColor4();
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="Vector3F"/> to <see cref="Color"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static explicit operator Color(Vector3F value)
    {
        return new Color(value.X, value.Y, value.Z, 1.0f);
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="Color3"/> to <see cref="Color"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static explicit operator Color(Color3 value)
    {
        return new Color(value.R, value.G, value.B, 1.0f);
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="Vector4F"/> to <see cref="Color"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static explicit operator Color(Vector4F value)
    {
        return new Color(value.X, value.Y, value.Z, value.W);
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="Color4"/> to <see cref="Color"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The result of the conversion.</returns>
    public static explicit operator Color(Color4 value)
    {
        return new Color(value.R, value.G, value.B, value.A);
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="System.Int32"/> to <see cref="Color"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>
    /// The result of the conversion.
    /// </returns>
    public static explicit operator int(Color value)
    {
        return value.ToRgba();
    }

    /// <summary>
    /// Performs an explicit conversion from <see cref="System.Int32"/> to <see cref="Color"/>.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>
    /// The result of the conversion.
    /// </returns>
    public static explicit operator Color(int value)
    {
        return new Color(value);
    }

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public override string ToString()
    {
        return ToString(CultureInfo.CurrentCulture);
    }

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <param name="format">The format to apply to each channel element (byte).</param>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public string ToString(string format)
    {
        return ToString(format, CultureInfo.CurrentCulture);
    }

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <param name="formatProvider">The format provider.</param>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public string ToString(IFormatProvider formatProvider)
    {
        return string.Format(formatProvider, toStringFormat, A, R, G, B);
    }

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <param name="format">The format to apply to each channel element (byte).</param>
    /// <param name="formatProvider">The format provider.</param>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public string ToString(string format, IFormatProvider formatProvider)
    {
        if (format == null)
            return ToString(formatProvider);

        return string.Format(formatProvider,
            toStringFormat,
            A.ToString(format, formatProvider),
            R.ToString(format, formatProvider),
            G.ToString(format, formatProvider),
            B.ToString(format, formatProvider));
    }

    /// <summary>
    /// Returns a hash code for this instance.
    /// </summary>
    /// <returns>
    /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
    /// </returns>
    public override int GetHashCode()
    {
        return Packed;
    }

    /// <summary>
    /// Determines whether the specified <see cref="Color"/> is equal to this instance.
    /// </summary>
    /// <param name="other">The <see cref="Color"/> to compare with this instance.</param>
    /// <returns>
    /// <c>true</c> if the specified <see cref="Color"/> is equal to this instance; otherwise, <c>false</c>.
    /// </returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool Equals(ref Color other)
    {
        return Packed == other.Packed;
    }

    /// <summary>
    /// Determines whether the specified <see cref="Color"/> is equal to this instance.
    /// </summary>
    /// <param name="other">The <see cref="Color"/> to compare with this instance.</param>
    /// <returns>
    /// <c>true</c> if the specified <see cref="Color"/> is equal to this instance; otherwise, <c>false</c>.
    /// </returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool Equals(Color other)
    {
        return Equals(ref other);
    }

    /// <summary>
    /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
    /// </summary>
    /// <param name="value">The <see cref="System.Object"/> to compare with this instance.</param>
    /// <returns>
    /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
    /// </returns>
    public override bool Equals(object value)
    {
        if (value is Color other)
            return Packed == other.Packed;

        return false;
    }

    /// <summary>
    /// Converts a float value between 0f and 1.0f to a byte value between 0 and 255, inclusive.
    /// </summary>
    /// <param name="value">The value to be converted.</param>
    /// <returns></returns>
    private static byte ToByte(float value)
    {
        return ToByte((int)(value * 255.0f));
    }

    public static byte ToByte(int value)
    {
        return (byte)(value < 0 ? 0 : value > 255 ? 255 : value);
    }

    /// <summary>Translates a <see cref="byte"/> array into a <see cref="Color"/> array.</summary>
    /// <param name="b">The <see cref="byte"/> array.</param>
    /// <returns>A <see cref="Color"/> array.</returns>
    public static Color[] ToColorArray(byte[] b)
    {
        if (b.Length % 4 > 0)
            throw new Exception("Byte data is invalid. Not aligned to 4-byte blocks. Each color element is 4 bytes.");

        Color[] r = new Color[b.Length / 4];
        int c = 0;
        for (int i = 0; i < b.Length; i += 4)
        {
            r[c] = new Color()
            {
                R = b[i],
                G = b[i+1],
                B = b[i+2],
                A = b[i+3],
            };

            c++;
        }

        return r;
    }

    /// <summary>Translates a <see cref="Color"/> array into a <see cref="byte"/> array.</summary>
    /// <param name="c">The <see cref="Color"/> array.</param>
    /// <returns>A <see cref="byte"/> array.</returns>
    public static byte[] ToByteArray(Color[] c)
    {
        byte[] r = new byte[c.Length * 4];
        int b = 0;
        for (int i = 0; i < c.Length; i++)
        {
            r[b++] = c[i].R;
            r[b++] = c[i].G;
            r[b++] = c[i].B;
            r[b++] = c[i].A;
        }

        return r;
    }

    /// <summary>
    /// Returns a new <see cref="Color"/> with the values of the provided color's components assigned based on their index.<para/>
    /// For example, a swizzle input of (1,1,3,1) on a <see cref="Color4"/> with RGBA values of 100,20,0,255, will return a <see cref="Color4"/> with values 20,20,255,20.
    /// </summary>
    /// <param name="col"></param>
    /// <param name="rIndex">The axis index of the source color to use for the new red value.</param>
    /// <param name="gIndex">The axis index of the source color to use for the new green value.</param>
    /// <param name="bIndex">The axis index of the source color to use for the new blue value.</param>
    /// <param name="aIndex">The axis index of the source color to use for the new alpha value.</param>
    /// <returns></returns>
    public static unsafe Color Swizzle(Color col, int rIndex, int gIndex, int bIndex, int aIndex)
    {
        return new Color()
        {
            R = *(&col.R + (rIndex * sizeof(int))),
            G = *(&col.G + (gIndex * sizeof(int))),
            B = *(&col.B + (bIndex * sizeof(int))),
            A = *(&col.A + (aIndex * sizeof(int))),
        };
    }
}
